/**
 *    Copyright 2006-2016 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.mybatis.generator.internal.db;

import static org.mybatis.generator.internal.util.JavaBeansUtil.getCamelCaseString;
import static org.mybatis.generator.internal.util.JavaBeansUtil.getValidPropertyName;
import static org.mybatis.generator.internal.util.StringUtility.composeFullyQualifiedTableName;
import static org.mybatis.generator.internal.util.StringUtility.isTrue;
import static org.mybatis.generator.internal.util.StringUtility.stringContainsSQLWildcard;
import static org.mybatis.generator.internal.util.StringUtility.stringContainsSpace;
import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;
import static org.mybatis.generator.internal.util.messages.Messages.getString;

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.mybatis.generator.api.FullyQualifiedTable;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.JavaTypeResolver;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.JavaReservedWords;
import org.mybatis.generator.config.ColumnOverride;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.GeneratedKey;
import org.mybatis.generator.config.PropertyRegistry;
import org.mybatis.generator.config.TableConfiguration;
import org.mybatis.generator.internal.ObjectFactory;
import org.mybatis.generator.logging.Log;
import org.mybatis.generator.logging.LogFactory;

import com.zzg.mybatis.generator.util.StringUtil;

/**
 * The Class DatabaseIntrospector.
 *
 * @author Jeff Butler
 */
public class DatabaseIntrospector {

	/** The database meta data. */
	private DatabaseMetaData databaseMetaData;

	/** The java type resolver. */
	private JavaTypeResolver javaTypeResolver;

	/** The warnings. */
	private List<String> warnings;

	/** The context. */
	private Context context;

	/** The logger. */
	private Log logger;

	/**
	 * Instantiates a new database introspector.
	 *
	 * @param context
	 *            the context
	 * @param databaseMetaData
	 *            the database meta data
	 * @param javaTypeResolver
	 *            the java type resolver
	 * @param warnings
	 *            the warnings
	 */
	public DatabaseIntrospector(Context context, DatabaseMetaData databaseMetaData, JavaTypeResolver javaTypeResolver,
			List<String> warnings) {
		super();
		this.context = context;
		this.databaseMetaData = databaseMetaData;
		this.javaTypeResolver = javaTypeResolver;
		this.warnings = warnings;
		logger = LogFactory.getLog(getClass());
	}

	/**
	 * Calculate primary key.
	 *
	 * @param table
	 *            the table
	 * @param introspectedTable
	 *            the introspected table
	 */
	private void calculatePrimaryKey(FullyQualifiedTable table, IntrospectedTable introspectedTable) {
		ResultSet rs = null;

		try {
			rs = databaseMetaData.getPrimaryKeys(table.getIntrospectedCatalog(), table.getIntrospectedSchema(),
					table.getIntrospectedTableName());
		} catch (SQLException e) {
			closeResultSet(rs);
			warnings.add(getString("Warning.15")); //$NON-NLS-1$
			return;
		}

		try {
			// keep primary columns in key sequence order
			Map<Short, String> keyColumns = new TreeMap<Short, String>();
			while (rs.next()) {
				String columnName = rs.getString("COLUMN_NAME"); //$NON-NLS-1$
				short keySeq = rs.getShort("KEY_SEQ"); //$NON-NLS-1$
				keyColumns.put(keySeq, columnName);
			}

			for (String columnName : keyColumns.values()) {
				introspectedTable.addPrimaryKeyColumn(columnName);
			}
		} catch (SQLException e) {
			// ignore the primary key if there's any error
		} finally {
			closeResultSet(rs);
		}
	}

	/**
	 * Close result set.
	 *
	 * @param rs
	 *            the rs
	 */
	private void closeResultSet(ResultSet rs) {
		if (rs != null) {
			try {
				rs.close();
			} catch (SQLException e) {
				// ignore
			}
		}
	}

	/**
	 * Report introspection warnings.
	 *
	 * @param introspectedTable
	 *            the introspected table
	 * @param tableConfiguration
	 *            the table configuration
	 * @param table
	 *            the table
	 */
	private void reportIntrospectionWarnings(IntrospectedTable introspectedTable, TableConfiguration tableConfiguration,
			FullyQualifiedTable table) {
		// make sure that every column listed in column overrides
		// actually exists in the table
		for (ColumnOverride columnOverride : tableConfiguration.getColumnOverrides()) {
			if (introspectedTable.getColumn(columnOverride.getColumnName()) == null) {
				warnings.add(getString("Warning.3", //$NON-NLS-1$
						columnOverride.getColumnName(), table.toString()));
			}
		}

		// make sure that every column listed in ignored columns
		// actually exists in the table
		for (String string : tableConfiguration.getIgnoredColumnsInError()) {
			warnings.add(getString("Warning.4", //$NON-NLS-1$
					string, table.toString()));
		}

		GeneratedKey generatedKey = tableConfiguration.getGeneratedKey();
		if (generatedKey != null && introspectedTable.getColumn(generatedKey.getColumn()) == null) {
			if (generatedKey.isIdentity()) {
				warnings.add(getString("Warning.5", //$NON-NLS-1$
						generatedKey.getColumn(), table.toString()));
			} else {
				warnings.add(getString("Warning.6", //$NON-NLS-1$
						generatedKey.getColumn(), table.toString()));
			}
		}

		for (IntrospectedColumn ic : introspectedTable.getAllColumns()) {
			if (JavaReservedWords.containsWord(ic.getJavaProperty())) {
				warnings.add(getString("Warning.26", //$NON-NLS-1$
						ic.getActualColumnName(), table.toString()));
			}
		}
	}

	/**
	 * Returns a List of IntrospectedTable elements that matches the specified table
	 * configuration.
	 *
	 * @param tc
	 *            the tc
	 * @return a list of introspected tables
	 * @throws SQLException
	 *             the SQL exception
	 */
	public List<IntrospectedTable> introspectTables(TableConfiguration tc) throws SQLException {

		// get the raw columns from the DB
		Map<ActualTableName, List<IntrospectedColumn>> columns = getColumns(tc);

		if (columns.isEmpty()) {
			warnings.add(getString("Warning.19", tc.getCatalog(), //$NON-NLS-1$
					tc.getSchema(), tc.getTableName()));
			return null;
		}

		removeIgnoredColumns(tc, columns);
		calculateExtraColumnInformation(tc, columns);
		applyColumnOverrides(tc, columns);
		calculateIdentityColumns(tc, columns);

		List<IntrospectedTable> introspectedTables = calculateIntrospectedTables(tc, columns);

		// now introspectedTables has all the columns from all the
		// tables in the configuration. Do some validation...

		Iterator<IntrospectedTable> iter = introspectedTables.iterator();
		while (iter.hasNext()) {
			IntrospectedTable introspectedTable = iter.next();

			if (!introspectedTable.hasAnyColumns()) {
				// add warning that the table has no columns, remove from the
				// list
				String warning = getString("Warning.1", introspectedTable.getFullyQualifiedTable().toString()); //$NON-NLS-1$
				warnings.add(warning);
				iter.remove();
			} else if (!introspectedTable.hasPrimaryKeyColumns() && !introspectedTable.hasBaseColumns()) {
				// add warning that the table has only BLOB columns, remove from
				// the list
				String warning = getString("Warning.18", introspectedTable.getFullyQualifiedTable().toString()); //$NON-NLS-1$
				warnings.add(warning);
				iter.remove();
			} else {
				// now make sure that all columns called out in the
				// configuration
				// actually exist
				reportIntrospectionWarnings(introspectedTable, tc, introspectedTable.getFullyQualifiedTable());
			}
		}

		return introspectedTables;
	}

	/**
	 * Removes the ignored columns.
	 *
	 * @param tc
	 *            the tc
	 * @param columns
	 *            the columns
	 */
	private void removeIgnoredColumns(TableConfiguration tc, Map<ActualTableName, List<IntrospectedColumn>> columns) {
		for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns.entrySet()) {
			Iterator<IntrospectedColumn> tableColumns = entry.getValue().iterator();
			while (tableColumns.hasNext()) {
				IntrospectedColumn introspectedColumn = tableColumns.next();
				if (tc.isColumnIgnored(introspectedColumn.getActualColumnName())) {
					tableColumns.remove();
					if (logger.isDebugEnabled()) {
						logger.debug(getString("Tracing.3", //$NON-NLS-1$
								introspectedColumn.getActualColumnName(), entry.getKey().toString()));
					}
				}
			}
		}
	}

	/**
	 * Calculate extra column information.
	 *
	 * @param tc
	 *            the tc
	 * @param columns
	 *            the columns
	 */
	private void calculateExtraColumnInformation(TableConfiguration tc,
			Map<ActualTableName, List<IntrospectedColumn>> columns) {
		StringBuilder sb = new StringBuilder();
		Pattern pattern = null;
		String replaceString = null;
		if (tc.getColumnRenamingRule() != null) {
			pattern = Pattern.compile(tc.getColumnRenamingRule().getSearchString());
			replaceString = tc.getColumnRenamingRule().getReplaceString();
			replaceString = replaceString == null ? "" : replaceString; //$NON-NLS-1$
		}

		for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns.entrySet()) {
			for (IntrospectedColumn introspectedColumn : entry.getValue()) {
				String calculatedColumnName;
				if (pattern == null) {
					calculatedColumnName = introspectedColumn.getActualColumnName();
				} else {
					Matcher matcher = pattern.matcher(introspectedColumn.getActualColumnName());
					calculatedColumnName = matcher.replaceAll(replaceString);
				}

				if (isTrue(tc.getProperty(PropertyRegistry.TABLE_USE_ACTUAL_COLUMN_NAMES))) {
					introspectedColumn.setJavaProperty(getValidPropertyName(calculatedColumnName));
				} else if (isTrue(tc.getProperty(PropertyRegistry.TABLE_USE_COMPOUND_PROPERTY_NAMES))) {
					sb.setLength(0);
					sb.append(calculatedColumnName);
					sb.append('_');
					sb.append(getCamelCaseString(introspectedColumn.getRemarks(), true));
					introspectedColumn.setJavaProperty(getValidPropertyName(sb.toString()));
				} else {
					introspectedColumn.setJavaProperty(getCamelCaseString(calculatedColumnName, false));
				}

				FullyQualifiedJavaType fullyQualifiedJavaType = javaTypeResolver.calculateJavaType(introspectedColumn);

				if (fullyQualifiedJavaType != null) {
					introspectedColumn.setFullyQualifiedJavaType(fullyQualifiedJavaType);
					introspectedColumn.setJdbcTypeName(javaTypeResolver.calculateJdbcTypeName(introspectedColumn));
				} else {
					// type cannot be resolved. Check for ignored or overridden
					boolean warn = true;
					if (tc.isColumnIgnored(introspectedColumn.getActualColumnName())) {
						warn = false;
					}

					ColumnOverride co = tc.getColumnOverride(introspectedColumn.getActualColumnName());
					if (co != null && stringHasValue(co.getJavaType()) && stringHasValue(co.getJavaType())) {
						warn = false;
					}

					// if the type is not supported, then we'll report a warning
					if (warn) {
						introspectedColumn.setFullyQualifiedJavaType(FullyQualifiedJavaType.getObjectInstance());
						introspectedColumn.setJdbcTypeName("OTHER"); //$NON-NLS-1$

						String warning = getString("Warning.14", //$NON-NLS-1$
								Integer.toString(introspectedColumn.getJdbcType()), entry.getKey().toString(),
								introspectedColumn.getActualColumnName());

						warnings.add(warning);
					}
				}

				if (context.autoDelimitKeywords()
						&& SqlReservedWords.containsWord(introspectedColumn.getActualColumnName())) {
					introspectedColumn.setColumnNameDelimited(true);
				}

				if (tc.isAllColumnDelimitingEnabled()) {
					introspectedColumn.setColumnNameDelimited(true);
				}
			}
		}
	}

	/**
	 * Calculate identity columns.
	 *
	 * @param tc
	 *            the tc
	 * @param columns
	 *            the columns
	 */
	private void calculateIdentityColumns(TableConfiguration tc,
			Map<ActualTableName, List<IntrospectedColumn>> columns) {
		GeneratedKey gk = tc.getGeneratedKey();
		if (gk == null) {
			// no generated key, then no identity or sequence columns
			return;
		}

		for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns.entrySet()) {
			for (IntrospectedColumn introspectedColumn : entry.getValue()) {
				if (isMatchedColumn(introspectedColumn, gk)) {
					if (gk.isIdentity() || gk.isJdbcStandard()) {
						introspectedColumn.setIdentity(true);
						introspectedColumn.setSequenceColumn(false);
					} else {
						introspectedColumn.setIdentity(false);
						introspectedColumn.setSequenceColumn(true);
					}
				}
			}
		}
	}

	/**
	 * Checks if is matched column.
	 *
	 * @param introspectedColumn
	 *            the introspected column
	 * @param gk
	 *            the gk
	 * @return true, if is matched column
	 */
	private boolean isMatchedColumn(IntrospectedColumn introspectedColumn, GeneratedKey gk) {
		if (introspectedColumn.isColumnNameDelimited()) {
			return introspectedColumn.getActualColumnName().equals(gk.getColumn());
		} else {
			return introspectedColumn.getActualColumnName().equalsIgnoreCase(gk.getColumn());
		}
	}

	/**
	 * Apply column overrides.
	 *
	 * @param tc
	 *            the tc
	 * @param columns
	 *            the columns
	 */
	private void applyColumnOverrides(TableConfiguration tc, Map<ActualTableName, List<IntrospectedColumn>> columns) {
		for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns.entrySet()) {
			for (IntrospectedColumn introspectedColumn : entry.getValue()) {
				ColumnOverride columnOverride = tc.getColumnOverride(introspectedColumn.getActualColumnName());

				if (columnOverride != null) {
					if (logger.isDebugEnabled()) {
						logger.debug(getString("Tracing.4", //$NON-NLS-1$
								introspectedColumn.getActualColumnName(), entry.getKey().toString()));
					}

					if (stringHasValue(columnOverride.getJavaProperty())) {
						introspectedColumn.setJavaProperty(columnOverride.getJavaProperty());
					}

					if (stringHasValue(columnOverride.getJavaType())) {
						introspectedColumn
								.setFullyQualifiedJavaType(new FullyQualifiedJavaType(columnOverride.getJavaType()));
					}

					if (stringHasValue(columnOverride.getJdbcType())) {
						introspectedColumn.setJdbcTypeName(columnOverride.getJdbcType());
					}

					if (stringHasValue(columnOverride.getTypeHandler())) {
						introspectedColumn.setTypeHandler(columnOverride.getTypeHandler());
					}

					if (columnOverride.isColumnNameDelimited()) {
						introspectedColumn.setColumnNameDelimited(true);
					}

					introspectedColumn.setGeneratedAlways(columnOverride.isGeneratedAlways());

					introspectedColumn.setProperties(columnOverride.getProperties());

				}
			}
		}
	}

	/**
	 * This method returns a Map<ActualTableName, List<ColumnDefinitions>> of
	 * columns returned from the database introspection.
	 *
	 * @param tc
	 *            the tc
	 * @return introspected columns
	 * @throws SQLException
	 *             the SQL exception
	 */
	private Map<ActualTableName, List<IntrospectedColumn>> getColumns(TableConfiguration tc) throws SQLException {
		String localCatalog;
		String localSchema;
		String localTableName;

		boolean delimitIdentifiers = tc.isDelimitIdentifiers() || stringContainsSpace(tc.getCatalog())
				|| stringContainsSpace(tc.getSchema()) || stringContainsSpace(tc.getTableName());

		if (delimitIdentifiers) {
			localCatalog = tc.getCatalog();
			localSchema = tc.getSchema();
			localTableName = tc.getTableName();
		} else if (databaseMetaData.storesLowerCaseIdentifiers()) {
			localCatalog = tc.getCatalog() == null ? null : tc.getCatalog().toLowerCase();
			localSchema = tc.getSchema() == null ? null : tc.getSchema().toLowerCase();
			localTableName = tc.getTableName() == null ? null : tc.getTableName().toLowerCase();
		} else if (databaseMetaData.storesUpperCaseIdentifiers()) {
			localCatalog = tc.getCatalog() == null ? null : tc.getCatalog().toUpperCase();
			localSchema = tc.getSchema() == null ? null : tc.getSchema().toUpperCase();
			localTableName = tc.getTableName() == null ? null : tc.getTableName().toUpperCase();
		} else {
			localCatalog = tc.getCatalog();
			localSchema = tc.getSchema();
			localTableName = tc.getTableName();
		}

		if (tc.isWildcardEscapingEnabled()) {
			String escapeString = databaseMetaData.getSearchStringEscape();

			StringBuilder sb = new StringBuilder();
			StringTokenizer st;
			if (localSchema != null) {
				st = new StringTokenizer(localSchema, "_%", true); //$NON-NLS-1$
				while (st.hasMoreTokens()) {
					String token = st.nextToken();
					if (token.equals("_") //$NON-NLS-1$
							|| token.equals("%")) { //$NON-NLS-1$
						sb.append(escapeString);
					}
					sb.append(token);
				}
				localSchema = sb.toString();
			}

			sb.setLength(0);
			st = new StringTokenizer(localTableName, "_%", true); //$NON-NLS-1$
			while (st.hasMoreTokens()) {
				String token = st.nextToken();
				if (token.equals("_") //$NON-NLS-1$
						|| token.equals("%")) { //$NON-NLS-1$
					sb.append(escapeString);
				}
				sb.append(token);
			}
			localTableName = sb.toString();
		}

		Map<ActualTableName, List<IntrospectedColumn>> answer = new HashMap<ActualTableName, List<IntrospectedColumn>>();

		if (logger.isDebugEnabled()) {
			String fullTableName = composeFullyQualifiedTableName(localCatalog, localSchema, localTableName, '.');
			logger.debug(getString("Tracing.1", fullTableName)); //$NON-NLS-1$
		}

		ResultSet rs = databaseMetaData.getColumns(localCatalog, localSchema, localTableName, "%"); //$NON-NLS-1$

		boolean supportsIsAutoIncrement = false;
		boolean supportsIsGeneratedColumn = false;
		ResultSetMetaData rsmd = rs.getMetaData();
		int colCount = rsmd.getColumnCount();
		for (int i = 1; i <= colCount; i++) {
			if ("IS_AUTOINCREMENT".equals(rsmd.getColumnName(i))) { //$NON-NLS-1$
				supportsIsAutoIncrement = true;
			}
			if ("IS_GENERATEDCOLUMN".equals(rsmd.getColumnName(i))) { //$NON-NLS-1$
				supportsIsGeneratedColumn = true;
			}
		}

		while (rs.next()) {
			IntrospectedColumn introspectedColumn = ObjectFactory.createIntrospectedColumn(context);

			introspectedColumn.setTableAlias(tc.getAlias());
			introspectedColumn.setJdbcType(rs.getInt("DATA_TYPE")); //$NON-NLS-1$
			introspectedColumn.setLength(rs.getInt("COLUMN_SIZE")); //$NON-NLS-1$
			introspectedColumn.setActualColumnName(rs.getString("COLUMN_NAME")); //$NON-NLS-1$
			introspectedColumn.setNullable(rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable); //$NON-NLS-1$
			introspectedColumn.setScale(rs.getInt("DECIMAL_DIGITS")); //$NON-NLS-1$
			introspectedColumn.setRemarks(rs.getString("REMARKS")); //$NON-NLS-1$
			introspectedColumn.setDefaultValue(rs.getString("COLUMN_DEF")); //$NON-NLS-1$

			List<String> ignoredColumns = tc.getIgnoreModelColumns();
			//在model中生成
			introspectedColumn.setGeneratedModel(true);
			
			if (ignoredColumns != null) {
				String javaName = StringUtil.underline2Camel(rs.getString("COLUMN_NAME"), true);
				if (ignoredColumns.contains(javaName)) {
					introspectedColumn.setGeneratedModel(false);
				}
			}
			if (supportsIsAutoIncrement) {
				introspectedColumn.setAutoIncrement("YES".equals(rs.getString("IS_AUTOINCREMENT"))); //$NON-NLS-1$ //$NON-NLS-2$
			}

			if (supportsIsGeneratedColumn) {
				introspectedColumn.setGeneratedColumn("YES".equals(rs.getString("IS_GENERATEDCOLUMN"))); //$NON-NLS-1$ //$NON-NLS-2$
			}

			ActualTableName atn = new ActualTableName(rs.getString("TABLE_CAT"), //$NON-NLS-1$
					rs.getString("TABLE_SCHEM"), //$NON-NLS-1$
					rs.getString("TABLE_NAME")); //$NON-NLS-1$

			List<IntrospectedColumn> columns = answer.get(atn);
			if (columns == null) {
				columns = new ArrayList<IntrospectedColumn>();
				answer.put(atn, columns);
			}

			columns.add(introspectedColumn);

			if (logger.isDebugEnabled()) {
				logger.debug(getString("Tracing.2", //$NON-NLS-1$
						introspectedColumn.getActualColumnName(), Integer.toString(introspectedColumn.getJdbcType()),
						atn.toString()));
			}
		}

		closeResultSet(rs);

		if (answer.size() > 1 && !stringContainsSQLWildcard(localSchema)
				&& !stringContainsSQLWildcard(localTableName)) {
			// issue a warning if there is more than one table and
			// no wildcards were used
			ActualTableName inputAtn = new ActualTableName(tc.getCatalog(), tc.getSchema(), tc.getTableName());

			StringBuilder sb = new StringBuilder();
			boolean comma = false;
			for (ActualTableName atn : answer.keySet()) {
				if (comma) {
					sb.append(',');
				} else {
					comma = true;
				}
				sb.append(atn.toString());
			}

			warnings.add(getString("Warning.25", //$NON-NLS-1$
					inputAtn.toString(), sb.toString()));
		}

		return answer;
	}

	/**
	 * Calculate introspected tables.
	 *
	 * @param tc
	 *            the tc
	 * @param columns
	 *            the columns
	 * @return the list
	 */
	private List<IntrospectedTable> calculateIntrospectedTables(TableConfiguration tc,
			Map<ActualTableName, List<IntrospectedColumn>> columns) {
		boolean delimitIdentifiers = tc.isDelimitIdentifiers() || stringContainsSpace(tc.getCatalog())
				|| stringContainsSpace(tc.getSchema()) || stringContainsSpace(tc.getTableName());

		List<IntrospectedTable> answer = new ArrayList<IntrospectedTable>();

		for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns.entrySet()) {
			ActualTableName atn = entry.getKey();

			// we only use the returned catalog and schema if something was
			// actually
			// specified on the table configuration. If something was returned
			// from the DB for these fields, but nothing was specified on the
			// table
			// configuration, then some sort of DB default is being returned
			// and we don't want that in our SQL
			FullyQualifiedTable table = new FullyQualifiedTable(
					stringHasValue(tc.getCatalog()) ? atn.getCatalog() : null,
					stringHasValue(tc.getSchema()) ? atn.getSchema() : null, atn.getTableName(),
					tc.getDomainObjectName(), tc.getAlias(),
					isTrue(tc.getProperty(PropertyRegistry.TABLE_IGNORE_QUALIFIERS_AT_RUNTIME)),
					tc.getProperty(PropertyRegistry.TABLE_RUNTIME_CATALOG),
					tc.getProperty(PropertyRegistry.TABLE_RUNTIME_SCHEMA),
					tc.getProperty(PropertyRegistry.TABLE_RUNTIME_TABLE_NAME), delimitIdentifiers, context);

			IntrospectedTable introspectedTable = ObjectFactory.createIntrospectedTable(tc, table, context);

			for (IntrospectedColumn introspectedColumn : entry.getValue()) {
				introspectedTable.addColumn(introspectedColumn);
			}

			calculatePrimaryKey(table, introspectedTable);

			enhanceIntrospectedTable(introspectedTable);

			answer.add(introspectedTable);
		}

		return answer;
	}

	/**
	 * This method calls database metadata to retrieve some extra information about
	 * the table such as remarks associated with the table and the type.
	 * 
	 * If there is any error, we just add a warning and continue.
	 * 
	 * @param introspectedTable
	 */
	private void enhanceIntrospectedTable(IntrospectedTable introspectedTable) {
		try {
			FullyQualifiedTable fqt = introspectedTable.getFullyQualifiedTable();

			ResultSet rs = databaseMetaData.getTables(fqt.getIntrospectedCatalog(), fqt.getIntrospectedSchema(),
					fqt.getIntrospectedTableName(), null);
			if (rs.next()) {
				String remarks = rs.getString("REMARKS"); //$NON-NLS-1$
				String tableType = rs.getString("TABLE_TYPE"); //$NON-NLS-1$
				introspectedTable.setRemarks(remarks);
				introspectedTable.setTableType(tableType);
			}
			closeResultSet(rs);
		} catch (SQLException e) {
			warnings.add(getString("Warning.27", e.getMessage())); //$NON-NLS-1$
		}
	}
}
